package command;

import effect.Effect;
import effect.EffectUtil;
import effect.Effect.EffectType;
import effect.Effect.TargetType;
import gui.BattleView;
import gui.CardViewPanel;
import gui.CenterPanel;
import gui.EffectPopupInfo;
import gui.GUICore;
import gui.GUIMapUtil;
import gui.RisingText;
import gui.SelectionController;
import gui.StatusPanel;
import gui.GUIConstants.ViewState;
import card.CreatureCard;

import java.awt.Color;
import java.util.Observable;

import audio.AePlayWave;

import card.BuildingCard;
import card.Card;
import card.CreatureCard;
import card.SpellCard;


import core.Battle;
import core.Constants;
import core.Core;
import core.MapUtil;
import core.Player;
import core.Rules;
import core.Tile;
import core.TurnController;
import core.Victory;
import core.Rules.Cost;
import core.Rules.Sound;

/**
 * 
 * @author Oscar
 *
 *A class with the purpose of executing commands.
 */

public class CommandExecutor extends Observable {
	private static CommandExecutor me;
	private static Core core;
	
	public static CommandExecutor getInstance(){
		if(me == null)
			me = new CommandExecutor();
		return me;
	}
	
	private CommandExecutor(){
		core=Core.getInstance();
		
		this.addObserver(StatusPanel.getInstance());
	}
	
	/**
	 * Executes a command, making the changes specified in the command to the core.
	 */
	public void executeCommand(Command c){
		if (c != null && CommandValidator.getInstance().validateCommand(c)){
			switch(c.commandType){
			case move:
				executeMoveCommand(c);
				break;
			case attack:
				executeAttackCommand(c);
				break;
			case activateAbility:
				executeActivateAbilityCommand(c);
				break;
			case playCard:
				executePlayCardCommand(c);
				break;
			case endTurn:
				executeEndTurnCommand(c);
				break;
			case claimTile:
				executeClaimTileCommand(c);
				break;
			case battleTarget:
				executeBattleTargetCommand(c);
				break;
			case battleStart:
				executeBattleStartCommand(c);
			case sendText:
				executeSendTextCommand(c);
			}
		}
			
		//TODO priority 2: support more commandtypes
		
		finalizeExecution();
		broadcast(core.getCurrentPlayer());
		refreshAuras();
	}
		
	

	private void executeSendTextCommand(Command c) {
		// TODO Auto-generated method stub
		
	}

	/**
	 * A method for refreshing all auras. The method first removes all the effects who
	 * are triggered by an aura. It then execute all the aura effects who are currently 
	 * in the game.
	 */
	private void refreshAuras() {
		for(Card c : Core.getInstance().getAllCards())
			for(Effect e : EffectUtil.getSubList(c.getEffects(), EffectType.triggeredByAura))
					c.getEffects().remove(e);
		for(Card c : Core.getInstance().getAllCards())
			for(Effect e : EffectUtil.getSubList(c.getEffects(), EffectType.auraGlobal))
					e.triggerAuraGlobal();
		for(Card c : Core.getInstance().getAllCards())
			for(Effect e : EffectUtil.getSubList(c.getEffects(), EffectType.auraTile))
					e.triggerAuraTile();
	}

	private void finalizeExecution(){
		switch(Constants.connectionState){
		case CONNECTED:
			if(core.getThisPlayer() == core.getCurrentPlayer())
				SelectionController.getInstance().deselect();
			break;
		case DISCONNECTED:
			SelectionController.getInstance().deselect();
			break;
		}
			
		CardViewPanel.getInstance().updateInfo();
		core.updateAll();
		
	}
	
	/**
	 * Move only supports Creatures so far.
	 * @param c
	 */
	private void executeMoveCommand(Command c){
		Card card=core.getCard(c.actingCardId);
		CreatureCard actor;
		if(card instanceof CreatureCard){
			actor = (CreatureCard)card;
			MapUtil.getTileOfCard(actor).removeCard(actor);
			MapUtil.getTile(c.targetTileId).addCreature(actor);
			actor.useAction();
		}
		else{
			//TODO invalid command reaction
		}
		
	}
	
	private void executeAttackCommand(Command c) {
		Tile attacker = MapUtil.getTileOfCard(core.getCard(c.actingCardId));
		Tile defender = MapUtil.getTile(c.getTargetTileId());
		triggerBattleRelatedEffects(attacker, defender);
		Battle.getInstance().initiateBattle(attacker, defender);
		GUICore.getInstance().currentView=ViewState.BATTLE;
		BattleView.getInstance().battleStarted();
	}
	
	private void executeActivateAbilityCommand(Command c){
		Card card = core.getCard(c.actingCardId);
		Effect e = card.getEffects().get(c.getEffectId());
		if(e.isTargetType(TargetType.none)){
			e.activate();
			String risingText = e.getName();
			CenterPanel.getInstance().addAnimation(new RisingText(GUIMapUtil.getGUITile(MapUtil.getTileOfCard(EffectUtil.getCard(e))).getPosition(), risingText, new Color(255, 214, 70), 1500));
		}
		else if (e.isTargetType(TargetType.tile)){
			Tile targetTile = MapUtil.getTile(c.getTargetTileId());
			e.activate(targetTile);
			String risingText = e.getName();
			CenterPanel.getInstance().addAnimation(new RisingText(GUIMapUtil.getGUITile(targetTile).getPosition(), risingText, new Color(255, 214, 70), 1500));
		}
		else if (e.isTargetType(TargetType.creatureCard)){
			CreatureCard targetCard = (CreatureCard)core.getCard(c.getTargetCardId());
			e.activate(targetCard);
		}
		else if (e.isTargetType(TargetType.buildingCard)){
			BuildingCard targetCard = (BuildingCard)core.getCard(c.getTargetCardId());
			e.activate(targetCard);
		}
		
		if(card instanceof SpellCard){
			core.getCurrentPlayer().removeCardFromHand(card);
			core.getCurrentPlayer().getDiscardPile().add(card);
		}
	}

	private void executePlayCardCommand(Command c) {
		if(Rules.playCardCost == Cost.always){
			core.getCurrentPlayer().substractGold(core.getCard(c.actingCardId).getGoldCost());
			core.getCurrentPlayer().substractMana(core.getCard(c.actingCardId).getManaCost());
		}
		Card card=core.getCard(c.actingCardId);
		card.playCard();
		if(card instanceof CreatureCard)
			MapUtil.getTile(c.targetTileId).addCreature((CreatureCard)card);
		if(card instanceof BuildingCard)
			MapUtil.getTile(c.targetTileId).addBuilding((BuildingCard)card);
		core.getCurrentPlayer().removeCardFromHand(card);
		String risingText = "-" + card.getGoldCost() + " gold";
		CenterPanel.getInstance().addAnimation(new RisingText(GUIMapUtil.getGUITile(MapUtil.getTile(c.targetTileId)).getPosition(), risingText, new Color(255, 214, 70), 1500));
		
		if(Rules.soundSwitch == Sound.on)
			new AePlayWave("coin.wav");
	}
	
	private void executeClaimTileCommand(Command c) {
		if(Rules.claimLandCost == Cost.always)
			core.getCurrentPlayer().substractGold(6);
		Card card=core.getCard(c.actingCardId);
		if(card instanceof CreatureCard){
			CreatureCard actor = (CreatureCard)card;
			MapUtil.getTileOfCard(actor).setOwningPlayer(actor.getOwner());
			actor.useAction();
		}
		String risingText = "-6 gold";
		CenterPanel.getInstance().addAnimation(new RisingText(GUIMapUtil.getGUITile(MapUtil.getTileOfCard(card)).getPosition(), risingText, new Color(255, 214, 70), 1500));
		if(Rules.soundSwitch == Sound.on)
			new AePlayWave("coin.wav");
	}
	
	private void executeEndTurnCommand(Command c) {
		TurnController.getInstance().changeTurn();
		SelectionController.getInstance().deselect(); // to make sure it deselects with player changing.
		Victory.haveWon(Core.getInstance().getCurrentPlayer());
	}
	
	private void executeBattleTargetCommand(Command c){
		Battle battle = Battle.getInstance();
		CreatureCard attacker;
		CreatureCard defender;
		Card a = core.getCard(c.getActingCardId());
		Card d = core.getCard(c.getTargetCardId());
		if(a instanceof CreatureCard && d instanceof CreatureCard){
			attacker = (CreatureCard)a;
			defender = (CreatureCard)d;
			battle.setTarget(attacker, defender);
		}	
	}
	
	private void executeBattleStartCommand(Command c){
		Battle battle = Battle.getInstance();
		Player player = core.getPlayers().get(c.getSourcePlayerId());
		int battlePlayerId = battle.getBattleIdOfPlayer(player);
		if(battlePlayerId != -1){
			battle.setReadyToFight(battlePlayerId);
		}
		if(battle.isReadyToStartBattle()){
			battle.fightBattle();
			BattleView.getInstance().setDamageTaken(battle.getDamageTaken());
		}
	}
	
	private void broadcast(Player p){
		this.setChanged();
		this.notifyObservers(p);
	}
	
	/**
	 * Checking if there is any battle related effects in the game by calling the three 
	 * methods triggerOnAttackEffects, triggerOnDefenseEffects, triggerOnBattleEffects.
	 * @param attack
	 * @param defend
	 */
	private void triggerBattleRelatedEffects(Tile attack, Tile defend){
		triggerOnAttackEffects(attack, defend);
	}
	
	/**
	 * Checks if any of the attacking creatures got an triggerOnAttackEffect. If they do,
	 * they calls the method triggerOnAttack for that effect.
	 * @param attack
	 * @param defend
	 */
	private void triggerOnAttackEffects(Tile attack, Tile defend) {
		//for(int i = 0; i < attack.getCreatures().size(); i++)
		for(CreatureCard c : attack.getCreatures())
			/*for(int j = 0; j < attack.getCreature(i).getEffects().size(); j++)
				if(attack.getCreature(i).getEffects().get(j).isEffectType(EffectType.triggerOnAttack))
					attack.getCreature(i).getEffects().get(j).triggerOnAttack(attack, defend);*/
			for(Effect e : EffectUtil.getSubList(c.getEffects(), EffectType.triggerOnAttackInitiation))
				e.triggerOnAttackInitiation(attack, defend);
	}
}